package com.xiao.aspect;

import com.alibaba.fastjson.JSON;
import com.xiao.annotation.SystemLog;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;

import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;

// 创建切面类
@Component// 注入到 spring 容器
@Aspect // 声明为 切面类
@Slf4j // Lombok 的日志打印
public class LogAspect {

    // 切入点
    @Pointcut("@annotation(com.xiao.annotation.SystemLog)")
    public  void pointCut(){

    }

    // 通知方法
    @Around("pointCut()") // Around 环绕通知;[round 圆的]
    public Object printLog(ProceedingJoinPoint joinPoint) throws Throwable {// joinPoint 相当于: 被增强的方法对象

        Object executeResult;

        try {// 如果在这里直接 try catch 掉,那么我们之前做的异常处理全会在这里处理
            this.handleBefore(joinPoint); // 调用前执行

            executeResult = joinPoint.proceed();// 目标方法的调用，返回执行结果; 这是一定要执行的，要不然被 AOP 的方法不会被调用

            this.handleAfter(executeResult);// 调用后执行

        } finally { // finally 因为不管有没有异常，我们都需要打印 "=======End======="
        // 结束后换行
            log.info("=======End=======" + System.lineSeparator()); //  System.lineSeparator() : 获取当前程序锁执行系统的换行符,因为不同系统(window、linux),换行符不同，所以不能写死
        }

        return executeResult;
    }

    private void handleAfter(Object executeResult) {
        // 打印出参
        log.info("Response : {}", JSON.toJSONString(executeResult));
    }

    private void handleBefore(ProceedingJoinPoint joinPoint) {

        // 当前线程的请求对象
        ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();// ....Holder 结尾。提供访问和保持请求上下文。 线程安全
        HttpServletRequest request =requestAttributes.getRequest();

        System.out.println(joinPoint.toString());
        // 获取 被增强方法上的注解对象
        SystemLog systemLog =getSystemLog(joinPoint);

        // 获取 方法对象
        Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();

        // 获取 方法入参
        Object[] args = joinPoint.getArgs();

        log.info("=======Start=======");
        // {} 占位符 : 理解为 mybatis 中的 预编译语句(preparedStatement) 中的  ?
// 打印请求 URL
        log.info("URL : {}",request.getRequestURL());// URL 不是 URI
// 打印描述信息
        log.info("BusinessName : {}", systemLog.businessName()); // 我们通过在注解里描述业务名称,在这里获取注解对象，获取注解内容就可以
// 打印 Http method
        log.info("HTTP Method : {}", request.getMethod());// GET / POST
// 打印调用 返回类型 执行类 的全路径以及执行方法
        log.info("Class Method : {}.{}", joinPoint.getSignature().getDeclaringTypeName(),method.getName());// {}.{} 只是最普通的拼接作用 【 等同于 joinPoint.toString()】
// 打印请求的 IP
        log.info("IP : {}",request.getRemoteHost());// 0:0:0:0:0:0:0:1 是ipv6的表现形式,对应ipv4来说相当于127.0.0.1,也就是本机
// 打印请求入参
        log.info("Request Args : {}", JSON.toJSONString(args));

    }

    private SystemLog getSystemLog(ProceedingJoinPoint joinPoint) {
        /**在这里学会 debug 调试，获取参数相关信息。
         * （当你搞不清，某个参数包含了什么具体内容的时候，可以使用 debug 调试查看,里面可以写复杂的 逻辑代码 例如: ((MethodSignature)joinPoint.getSignature()).getMethod().getName()// 获取方法名
         * 平时 是重启 程序 测试，现在 可以使用 debug 调试
         */

        // 1. 获取 方法签名
        MethodSignature signature = (MethodSignature)joinPoint.getSignature(); // 获取方法签名

        // 2. 获取 方法
        Method method = signature.getMethod();

        // 3. 获取 注解
        SystemLog annotation = method.getAnnotation(SystemLog.class);

        return annotation;
    }


}
